定义
软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改时封闭的。在软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。
程序一旦开发完成,程序中一个类的实只应该因错误而被修改,新的或者改变的特性应该通过新建不同的类实现,新建的类可以通过继承的方式来重用原类的代码。
举例
在上一节中实现的代码是通过内存缓存解决了每次从网络加载图片的问题,但是Android应用的内存很有限,且具有易失性。那么我们可以引入SD卡缓存,这样下载过的图片就会缓存在本地:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class DiskCache { static String cacheDir = "sdcard/cache/"; public Bitmap get(String url) { return BitmapFactory.decodeFile(cacheDir + url); } public void put(String url, Bitmap bmp) { FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream(cacheDir + url); bmp.compress(CompressFormat.PNG, 100, fileOutputStream); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { if (fileOutputStream != null) { try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
|
因为需要将图片缓存在SD中,所以ImageLoader代码需要更新:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class ImageLoader { ImageCache mImageCache = new ImageCache(); DiskCache mDiskCache = new DiskCache(); boolean isUseDiskCache = false; ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); public void displayImage(final String url, final ImageView imageView) { Bitmap bitmap = isUseDiskCache ? mDiskCache.get(url) : mImageCache.get(url); if (bitmap != null) { imageView.setImageBitmap(bitmap); return; } } public void useDiskCache(boolean useDiskCache) { this.isUseDiskCache = useDiskCache; } }
|
从上述代码中可以看出,仅仅新增了一个DiskCache类和往ImageLoader类中加入了少量代码就添加了SD卡缓存的功能,用户可以通过useDiskCache()方法来对使用哪种缓存进行设置。
但是,上面的代码还是存在很大问题,扩展性太差了,而且每次都需要修改ImageLoader的代码,并且用户也不能实现自定义缓存。
现在我们将上面的代码进行重构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| public class ImageLoader { ImageCache mImageCache = new ImageCache(); ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); public void setImageCache(ImageCache cache) { this.mImageCache = cache; } public void displayImage(final String url, final ImageView imageView) { Bitmap bitmap = mImageCache.get(url); if (bitmap != null) { imageView.setImageBitmap(bitmap); return; } submitLoadRequest(url, imageView); } private void submitLoadRequest(final String url, final ImageView imageView) { imageView.setTag(url); mExecutorService.submit(new Runnable() { @Override public void run() { Bitmap bitmap = downloadImage(url); if (bitmap == null) { return; } if (imageView.getTag().equals(url)) { imageView.setImageBitmap(bitmap); } mImageCache.put(url, bitmap); } }); } public Bitmap downloadImage(String url) { Bitmap bitmap = null; try { URL url = new URL(imageUrl); final HttpURLConnection conn = (HttpURLConnection) url.openConnection(); bitmap = BitmapFactory.decodeStream(conn.getInputStream()); conn.disconnect(); } catch (Exception e) { e.printStackTrace(); } return bitmap; } }
|
这里的ImageCache并不是原来的ImageCache,而是抽象为了一个接口:
1 2 3 4
| public interface ImageCache { public Bitmap get(String url); public void put(String url, Bitmap bitmap); }
|
这样不同的缓存策略只需要实现这个接口即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class MemoryCache implements ImageCache { public MemoryCache() {} @Override public Bitmap get(String url) { retutn mMemoryCache.get(url); } @Override public void put(String url, Bitmap bitmap) { mMemoryCache.put(url, bitmap); } } public class DiskCache implements ImageCache { public DiskCache() {} @Override public Bitmap get(String url) { } @Override public void put(String url, Bitmap bitmap) { } }
|
我们还可以发现在ImageLoader类中增加了一个setImageCache(ImageCache cache)方法,用户可以通过该方法设置缓存实现,也就是通常说的依赖注入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ImageLoader imageLoader = new ImageLoader(); imageLoader.setImageCache(new MemoryCache()); imageLoader.setImageCache(new DiskCache()); imageLoader.setImageCache(new ImageCache() { @Override public void put(String url, Bitmap bitmap) { } @Override public Bitmap get(String url) { } });
|
在上述代码中通过setImageCache(ImageCache cache)方法注入不同的缓存实现,这样不仅能够使ImageLoader更简单、健壮,也使得ImageLoader的可扩展性、灵活性更高。
MemoryCache、DiskCache缓存图片的原理不同,但是它们的一个共同点就是实现了ImageCache接口,当用户需要自定义实现缓存策略时,只需要新建一个实现ImageLoader接口的类,然后构造该类的对象,并且通过setImageCache(ImageCache cache)注入到ImageLoader中,这样ImageLoader就实现了千变万化的缓存策略,且扩展这些缓存策略并不会导致ImageLoader类的修改。
总结
软件中的对象应该对于扩展是开放的,对于修改是封闭的,实现开闭原则的重要手段就是通过抽象。当软件需要变化时,应该尽量通过扩展的方式实现变化,而不是通过修改已有代码实现。